﻿//example   API限流 过滤器   改用next模式
 //console.log("加载限流过滤器aax");
var config;
var redisclient; // redisclient.getclient() 可直接获取连接
module.exports = {
  grade: 0,
  //权重 值越小的优先执行
  check: function(req,res) { //检查是否符合过滤器要求  返回 true或false
    return true //所有请求 都执行限流策略
  },
  callback: function(req, res, next) { //符合过滤器要求的请求，会执行此回调, 给予通行时需调用next执行下一个过滤器
    let _this = this;
    let apikey = req.query.apikey || req.body && req.body.apikey;
    let tactics = config.apitactics;//api限流策略
    tactics = tactics.filter(function(v) {//取符合此次请求的限流策略
      if (v.servename == "ALL" || req.servername == v.servename) return true;
    })
    if (tactics.length) {
      let mult = redisclient.getclient().multi();
      let day = new Date().totext("yyyy-MM-DD");
      let time = new Date().getTime();
      tactics.some(function(v) { //检查是否达到限流条件   v.apitype=1 为所有apikey,0为单个apikey
        let rkey = "tactics:" + v.servename + ":" + (v.apitype ? "": apikey);
        mult.hmget(rkey, "settime", "traffic_permit", day)//  读 1585133843169   剩余可用的令牌(qps)  2020-03-25
      })
      let P = new Promise(function(resolve, reject) {
        let mult2 = redisclient.getclient().multi();
        mult.exec(function(err, result) {
          //console.log("aa2232",result);
          if (!err) {
            let f = result.some(function(v, i) {
              if(tactics[i].qps==-1&&tactics[i].number == -1){
                return false;
              }
              let rkey = "tactics:" + tactics[i].servename + ":" + (tactics[i].apitype ? "": apikey);
              //console.log(rkey,v);
              let retd = {
                succeed: 1,
                server: tactics[i].servename,
                tacticsid: i
              };
              let settime = v[0] || 0;// 此条策略上次设置令牌的时间 例 1585133843169
              //console.log(time-settime);
              if (time - settime > 200) { //需要添加通行证，且此策略QPS可以放行
                if (tactics[i].number == -1) { //日调用无限次
                } else if (v[2] > tactics[i].number) { //日调用次数达上限
                  retd.succeed = 0;
                  retd.messg = "当日调用次数已达上限";
                }
                let qps=tactics[i].qps;
                if(qps==-1){
                  qps=1000
                }
                let traper = (parseInt(v[1]) || 0) + parseInt(qps / 5) - 1; //添加本次追加令牌数后的总令牌数
                //console.log("剩余令牌",traper);
                if (traper > qps) {
                  traper = qps
                }
                if (retd.succeed == 0) {
                  resolve(retd);
                  mult2.hmset(rkey, ["settime", time, "traffic_permit", traper])
                  return true;
                } else {
                  mult2.hmset(rkey, ["settime", time, "traffic_permit", traper, day, parseInt(v[2] || 0) + 1])
                }
              } else { //无需添加通行证，扣除一个令牌，增加一次日调用次数
                let f;
                if (v[1] - 1 > 0) { //还有可用的令牌
                  if (tactics[i].number == -1) { //日调用无限次
                    
                  } else if (v[2] > tactics[i].number) { //日调用次数达上限
                    retd.succeed = 0;
                    retd.messg = "当日调用次数已达上限";
                  }
                } else if(tactics[i].qps!==-1) { //无可用令牌
                  retd.succeed = 0;
                  retd.messg = "QPS已达上限";
                }
                if (retd.succeed == 0) {
                  resolve(retd);
                  return true;
                } else {
                  mult2.hmset(rkey, ["traffic_permit", v[1] - 1, day, parseInt(v[2] || 0) + 1]);
                }
              }
            })
            mult2.exec();
            if (!f) { //没有生效的策略
              resolve({
                succeed: 1
              }) //放行
            }
          }
        })
      })
      P.then(function(o) {
        if (!o.succeed) { //策略生效
          //console.log(o);
          res.json(o);
        } else {
          next()
        }
      }).
      catch(function(err) {
        console.log(err);
        next()
      })
    } else {
      next()
    }
    //console.log(redisclient.getclient())
  },
  config: function(obj) { //如不需要读取配置 可不设置此属性
    config = obj
  },
  setredis: function(client) { //如不需要使用redis  可不设置此属性
    redisclient = client;
  }
}